Skip to content

Resolve frontend CI failures from react-hooks/set-state-in-effect across data-loading pages - #344

Merged
bg-playground merged 2 commits into
mainfrom
copilot/fix-react-hooks-set-state-errors
May 10, 2026
Merged

Resolve frontend CI failures from react-hooks/set-state-in-effect across data-loading pages#344
bg-playground merged 2 commits into
mainfrom
copilot/fix-react-hooks-set-state-errors

Conversation

Copilot AI commented May 10, 2026

Copy link
Copy Markdown
Contributor

After the eslint-plugin-react-hooks bump, Frontend CI started failing on 11 react-hooks/set-state-in-effect violations. The failures came from effects synchronously invoking callbacks that perform immediate setState (e.g., setLoading(true)), plus one direct state update inside an effect.

  • Scope of fix (11 violations / 8 files)

    • Updated effect call sites in:
      • AuditLogPage.tsx (3)
      • ManualLinksPage.tsx (1)
      • MetricsDashboardPage.tsx (1)
      • RequirementsPage.tsx (1)
      • SuggestionDashboard.tsx (2)
      • TestCasesPage.tsx (1)
      • TraceabilityMatrixPage.tsx (1)
      • UserManagementPage.tsx (1)
  • Effect refactor pattern

    • Replaced direct synchronous effect calls like loadData() / loadUsers() / loadEntries(page) with inline async IIFEs invoked via void, keeping behavior the same while avoiding synchronous setState in effect bodies.
    • Applied this consistently to initial loads and filter-driven reload paths.
  • Direct state update in effect

    • In SuggestionDashboard, changed top-level setFocusedIndex(-1) in useEffect to microtask scheduling so the effect body itself is not synchronously mutating state.
useEffect(() => {
  let cancelled = false;
  void (async () => {
    if (!cancelled) {
      await loadData();
    }
  })();
  return () => {
    cancelled = true;
  };
}, [loadData]);
Original prompt

Problem

The Frontend CI workflow is failing due to 11 react-hooks/set-state-in-effect ESLint errors introduced after bumping eslint-plugin-react-hooks in PR #340. The rule fires when setState is called synchronously within a useEffect body — including via functions that themselves call setState synchronously (e.g. setLoading(true) as the first line of a useCallback).

The failing job log (.github/workflows/frontend-ci.yml) shows:

/frontend/src/pages/AuditLogPage.tsx
  105:5  error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect
  109:5  error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect
  128:7  error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/ManualLinksPage.tsx
  44:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/MetricsDashboardPage.tsx
  26:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/RequirementsPage.tsx
  45:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/SuggestionDashboard.tsx
  171:5  error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect
  176:5  error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/TestCasesPage.tsx
  46:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/TraceabilityMatrixPage.tsx
  26:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

/frontend/src/pages/UserManagementPage.tsx
  57:5   error  Calling setState synchronously within an effect  react-hooks/set-state-in-effect

Root Cause

The pattern used across all these pages looks like:

const loadData = useCallback(async () => {
  setLoading(true);       // ← synchronous setState call
  const data = await api.fetch();
  setData(data);
  setLoading(false);
}, [deps]);

useEffect(() => {
  loadData();             // ← linter sees this as synchronously invoking setState via loadData
}, [loadData]);

The linter traces through loadData() and sees that it synchronously calls setLoading(true) (before any await), flagging it as "setState called synchronously within an effect."

For SuggestionDashboard.tsx there is also a direct setFocusedIndex(-1) call at the top level of a useEffect.

Required Fix

Fix all 11 violations across the 8 files. The preferred approach (in order of preference):

  1. Restructure useEffect to define an inline async function and call it, e.g.:

    useEffect(() => {
      let cancelled = false;
      (async () => {
        if (!cancelled) await loadData();
      })();
      return () => { cancelled = true; };
    }, [loadData]);

    This makes the effect body itself non-synchronous (no top-level setState call), which satisfies the rule.

  2. Or use void operator if that satisfies the installed version of the rule:

    useEffect(() => {
      void loadData();
    }, [loadData]);
  3. For direct setState calls (like setFocusedIndex(-1) in SuggestionDashboard), wrap in a microtask or use queueMicrotask/setTimeout(fn, 0) — or use an inline async IIFE. Alternatively, if the functional pattern is clearly intentional and idiomatic React, a targeted // eslint-disable-next-line react-hooks/set-state-in-effect is acceptable as a last resort for that specific line.

Do NOT use blanket eslint-disable file-level comments. Fix each violation precisely.

Files to Fix

  • frontend/src/pages/AuditLogPage.tsx — 3 errors (lines ~105, ~109, ~128)
  • frontend/src/pages/ManualLinksPage.tsx — 1 error (line ~44)
  • frontend/src/pages/MetricsDashboardPage.tsx — 1 error (line ~26)
  • frontend/src/pages/RequirementsPage.tsx — 1 error (line ~45)
  • frontend/src/pages/SuggestionDashboard.tsx — 2 errors (lines ~171, ~176)
  • frontend/src/pages/TestCasesPage.tsx — 1 error (line ~46)
  • frontend/src/pages/TraceabilityMatrixPage.tsx — 1 error (line ~26)
  • frontend/src/pages/UserManagementPage.tsx — 1 error (line ~57)

Acceptance Criteria

  • npm run lint passes with 0 errors in the frontend directory
  • No functional behaviour changes — all data-loading, filtering, and focus-management logic must work identically to before
  • No blanket eslint-disable comments added at file level
  • PR targets the main branch

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: yes, please. Thank you!

Assistant: Good news and a time-saver:

Assistant: Good news on both:

This pull request was created from Copilot chat.

Copilot AI changed the title [WIP] Fix react-hooks/set-state-in-effect ESLint errors Resolve frontend CI failures from react-hooks/set-state-in-effect across data-loading pages May 10, 2026
Copilot AI requested a review from bg-playground May 10, 2026 00:11
@bg-playground
bg-playground marked this pull request as ready for review May 10, 2026 00:15
@bg-playground
bg-playground merged commit c09099e into main May 10, 2026
8 checks passed
@bg-playground
bg-playground deleted the copilot/fix-react-hooks-set-state-errors branch May 10, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants